package com.cyw.common.utils;

import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.cyw.common.domain.dto.DateInfo;
import com.cyw.common.exceptions.BaseException;
import org.apache.commons.lang.StringUtils;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAdjusters;
import java.util.*;

/**
 * 时间相关处理工具类
 * @author cyw
 */
public class DateUtils {

    public static final String DEFAULT_PATTERN = "yyyy-MM-dd HH:mm:ss";
    public static final String DEFAULT_PATTERN_yyyyMMddHHmmss = "yyyyMMddHHmmss";
    public static final String DEFAULT_PATTERN_4y2M2d2H2m2s3S = "yyyyMMddHHmmssSSS";
    public static final String DEFAULT_PATTERN_HH_MM = "yyyy-MM-dd HH:mm";
    public static final String DEFAULT_PATTERN_MM_dd_HH_MM = "MM-dd HH:mm";
    public static final String DEFAULT_PATTERN_DAY = "yyyy-MM-dd";
    public static final String DEFAULT_PATTERN_YYYY_MM = "yyyy-MM";
    public static final String DEFAULT_PATTERN_MM_DD = "MM-dd";
    public static final String DEFAULT_PATTERN_SLASH_MM_DD = "MM/dd";
    public static final String DEFAULT_PATTERN_MM_DOT_DD = "MM.dd";
    public static final String DEFAULT_PATTERN_SSS = "yyyy-MM-dd HH:mm:ss.SSS";
    public static final String DEFAULT_PATTERN_YYYY = "yyyy";
    public static final String DEFAULT_PATTERN_YYYYMMDD = "yyyyMMdd";
    public static final String DEFAULT_PATTERN_DAY_T = "yyyy-MM-dd'T'HH:mm:ss";

    private static final ZoneOffset GMT_PLUS_8 = ZoneOffset.ofHours(8);

    /**
     * 取时分秒
     */
    public static final String DEFAULT_PATTERN_HMS = "HH:mm:ss";

    public static final String DEFAULT_TODAY_START = " 00:00:00";
    public static final String DEFAULT_TODAY_END = " 23:59:59";

    // 日期天数范围常量
    public static final int DAY_1 = 1;
    public static final int DAY_30 = 30;
    public static final int DAY_60 = 60;
    public static final int DAY_90 = 90;
    public static final int DAY_180 = 180;
    public static final int DAY_365 = 365;

    // 小时常量数据
    public static final int HOURS_12 = 12;

    /**
     * 最大时间
     */
    private static final LocalTime[] MAX_LOCAL_TIME_ARRAY = new LocalTime[]{
            LocalTime.of(23, 59, 59, 000_000_000),
            LocalTime.of(23, 59, 59, 900_000_000),
            LocalTime.of(23, 59, 59, 990_000_000),
            LocalTime.of(23, 59, 59, 999_000_000),
            LocalTime.of(23, 59, 59, 999_900_000),
            LocalTime.of(23, 59, 59, 999_990_000),
            LocalTime.of(23, 59, 59, 999_999_000),
    };

    /**
     * ------------------------------------ java.util.date Start ------------------------------------
     */
    public static String formatDate(Date date, String format) {
        if (date == null) {
            return "";
        }
        try {
            DateFormat df = new SimpleDateFormat(format);
            return df.format(date);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("格式错误:" + format);
        }
    }


    public static String formatDate2Day(Date date) {
        return formatDate(date, DEFAULT_PATTERN_DAY);
    }

    public static Date formatDate2Date(Date date, String format) {
        return parseDate(formatDate(date, format), format);
    }

    public static Date parseDate(String dateStr, String format) {
        if (StringUtils.isEmpty(dateStr)) {
            return null;
        }
        DateFormat df = new SimpleDateFormat(format);
        try {
            return df.parse(dateStr);
        } catch (ParseException e) {
            LogUtils.printError(e);
            throw new BaseException("日期格式错误:" + dateStr);
        }
    }


    public static Date parseDateDay(String dateStr) {
        return parseDate(dateStr, DEFAULT_PATTERN_DAY);
    }

    /**
     * 生成日期数组
     *
     * @param startDate 包含
     * @param endDate   包含
     * @return
     */
    public static List<Date> generateDateList(Date startDate, Date endDate) {
        Assert.isTrue(startDate.before(endDate) || startDate.equals(endDate), "开始日期不能大于结束日期");
        Calendar start = new GregorianCalendar();
        start.setTime(startDate);
        Calendar end = new GregorianCalendar();
        end.setTime(endDate);

        List<Date> dateList = new ArrayList<>();
        while (start.before(end) || start.equals(end)) {
            Date result = start.getTime();
            dateList.add(result);
            start.add(Calendar.DATE, 1);
        }
        return dateList;
    }

    /**
     * ------------------------------------ java.util.date End------------------------------------
     */

    /**
     * ------------------------------------ WEEK Start ------------------------------------
     */
    public static final String[] WEEK_NAME_ARRAY = {"占位符", "周一", "周二", "周三", "周四", "周五", "周六", "周日"};
    public static final String[] WEEK_SIMPLE_NAME_SIMPLE = {"占位符", "一", "二", "三", "四", "五", "六", "日"};

    /**
     * 获取星期数字， 0-周一，1-周二 ~~~ 5-周六，6-周日
     * @param date 日期
     * @return [0~6]
     */
    public static Integer getWeekIndex(LocalDate date) {
        return date.getDayOfWeek().getValue() - 1;
    }

    /**
     * 获取星期数字， 1-周一，2-周二 ~~~ 6-周六，7-周日
     * @param date 日期
     * @return [1~7]
     */
    public static int getWeekNum(LocalDate date) {
        return date.getDayOfWeek().getValue();
    }

    /**
     * 获取星期名称
     * @param date 日期
     * @return 名称 "周一", "周二", "周三", "周四", "周五", "周六", "周日"
     */
    public static String getWeekName(LocalDate date) {
        if (date == null) {
            return null;
        }
        return WEEK_NAME_ARRAY[getWeekNum(date)];
    }

    /**
     * 获取星期名称
     * @param date 日期
     * @return 名称 "一", "二", "三", "四", "五", "六", "日"
     */
    public static String getWeeSimpleName(LocalDate date) {
        if (date == null) {
            return null;
        }
        return WEEK_SIMPLE_NAME_SIMPLE[getWeekNum(date)];
    }


    /**
     * 是否周未，周五和周六为周末
     * @param date 日期
     * @return 结果
     */
    public static boolean isWeekend(LocalDate date) {
        if (date == null) {
            return false;
        }
        DayOfWeek week = date.getDayOfWeek();
        return week == DayOfWeek.FRIDAY || week == DayOfWeek.SATURDAY;
    }

    /**
     * ------------------------------------ WEEK END ------------------------------------
     */

    /**
     * ------------------------------------ java.time.LocalDate, LocalDateTime Start ------------------------------------
     */

    /**
     * ------------------------------------ java.util.date 与 java.time.* 的转换 Start ------------------------------------
     */
    /**
     * date -> LocalDate
     * @param date date
     * @return LocalDate
     */
    public static LocalDate date2LocalDate(Date date) {
        return date == null ? null : LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).toLocalDate();
    }

    /**
     * LocalDate -> Date
     * @param localDate LocalDate
     * @return date
     */
    public static Date localDate2Date(LocalDate localDate) {
        return localDate == null ? null : Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }

    /**
     * LocalDateTime转换为相同时刻的java.util.Date
     *
     * @param localDateTime localDateTime
     * @return Date
     */
    public static Date localDateTime2Date(LocalDateTime localDateTime) {
        return localDateTime == null ? null : Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    /**
     * java.util.Date转换为相同时刻的LocalDateTime
     *
     * @param date date
     * @return LocalDateTime
     */
    public static LocalDateTime date2localDateTime(Date date) {
        return date == null ? null : LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
    }

    /**
     * ------------------------------------ java.util.date 与 java.time.* 的转换 End------------------------------------
     */


    /**
     * ------------------------------------ parse & format Start ------------------------------------
     */

    /**
     * 解析日期字符串，默认：yyyy-MM-dd
     * @param dateStr 日期字符串
     * @return LocalDate
     */
    public static LocalDate parseLocalDate(String dateStr) {
        return BaseUtils.isEmpty(dateStr) ? null : LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(DEFAULT_PATTERN_DAY));
    }

    /**
     * 比较时间前后
     * -1 dateA<dateB ,0相等 ,1 dateA> dateB
     *
     * @param dateA
     * @param dateB
     * @param format
     * @return 如果参数dateA等于dateB，则值为0；如果此dateA在dateB参数之前，则小于0的值；如果dateA在dateB参数之后，则返回大于0的值
     */
    public static int compareTo(String dateA, String dateB, String format) {
        LocalDateTime newDateA = parseLocalDateTime(dateA, format);
        LocalDateTime newDateB = parseLocalDateTime(dateB, format);

        return newDateA.compareTo(newDateB);
    }

    /**
     * 解析日期字符串
     * @param dateStr 日期字符串
     * @return LocalDate
     */
    public static LocalDate parseLocalDate(String dateStr, String pattern) {
        return BaseUtils.isEmpty(dateStr) ? null : LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 按指定格式解析时间字符串
     * @param time 时间字符串
     * @param pattern 格式
     * @return LocalDateTime
     */
    public static LocalDateTime parseLocalDateTime(String time, String pattern) {
        return BaseUtils.isEmpty(time) ? null : LocalDateTime.parse(time, DateTimeFormatter.ofPattern(pattern));
    }

    /**
     * 解析时间字符串，默认：yyyy-MM-dd HH:mm:ss
     * @param time 时间字符串
     * @return LocalDateTime
     */
    public static LocalDateTime parseLocalDateTime(String time) {
        return parseLocalDateTime(time, DEFAULT_PATTERN);
    }

    /**
     * 默认格式化localDate为：yyyy-MM-dd
     * @param localDate LocalDate
     * @return 格式化字符串
     */
    public static String formatLocalDate(LocalDate localDate) {
        return formatLocalDate(localDate, DEFAULT_PATTERN_DAY);
    }

    /**
     * 格式化localDate为指定pattern
     * @param date localDate
     * @param format 格式
     * @return 结果
     */
    public static String formatLocalDate(LocalDate date, String format) {
        if (date == null || BaseUtils.isEmpty(format)) {
            return "";
        }
        return date.format(DateTimeFormatter.ofPattern(format));
    }

    /**
     * 格式化localDate为：yyyy-MM-dd HH:mm:ss
     * @param localDateTime LocalDateTime
     * @return 格式化字符串
     */
    public static String formatLocalDateTime(LocalDateTime localDateTime) {
        return formatLocalDateTime(localDateTime, DEFAULT_PATTERN);
    }

    /**
     * 格式化localDate为：yyyy-MM-dd HH:mm:ss
     * @param localDateTime LocalDateTime
     * @return 格式化字符串
     */
    public static String formatLocalDateTime(LocalDateTime localDateTime, String pattern) {
        if (localDateTime == null || BaseUtils.isEmpty(pattern)) {
            return "";
        }
        return localDateTime.format(DateTimeFormatter.ofPattern(pattern));
    }
    /**
     * ------------------------------------ parse & format End ------------------------------------
     */


    /**
     * 获取本月第一天
     * @param localDate 日期
     * @return 第一天
     */
    public static LocalDate monthFirstDate(LocalDate localDate) {
        return localDate == null ? null : localDate.with(TemporalAdjusters.firstDayOfMonth());
    }

    /**
     * 本月最后一天
     * @param localDate 日期
     * @return 最后一天
     */
    public static LocalDate monthLastDate(LocalDate localDate) {
        return localDate == null ? null : localDate.with(TemporalAdjusters.lastDayOfMonth());
    }

    /**
     * 当年第一天
     * @param localDate 日期
     * @return 当年第一天
     */
    public static LocalDate yearFirstDate(LocalDate localDate) {
        return localDate == null ? null : localDate.with(TemporalAdjusters.firstDayOfYear());
    }

    /**
     * 当年最后一天
     * @param localDate 日期
     * @return 当年最后一天
     */
    public static LocalDate yearEndDate(LocalDate localDate) {
        return localDate == null ? null : localDate.with(TemporalAdjusters.lastDayOfYear());
    }

    /**
     * 获取当天结束时刻,秒后为0
     * @param time 要处理的日期
     * @return 处理后的日期
     */
    public static LocalDateTime getDayEndTime(LocalDate time) {
        return time == null ? null : getDayEndTime(time, 0);
    }

    /**
     * 获取当天结束时刻，并指定秒后保留的位数
     * @param time
     * @param nanoLevel 0~6，对应秒后保留多少位
     * @return
     */
    public static LocalDateTime getDayEndTime(LocalDate time, int nanoLevel) {
        Assert.isTrue(nanoLevel >= 0 && nanoLevel < MAX_LOCAL_TIME_ARRAY.length, "nanoLevel取值范围[0~" + (MAX_LOCAL_TIME_ARRAY.length - 1) + "]");
        return time == null ? null : time.atTime(MAX_LOCAL_TIME_ARRAY[nanoLevel]);
    }

    /**
     * 获取当天结束时刻,秒后为0
     * @param time 要处理的日期
     * @return 处理后的日期
     */
    public static LocalDateTime getDayEndTime(LocalDateTime time) {
        return time == null ? null : getDayEndTime(time, 0);
    }

    /**
     * 获取当天结束时刻，并指定秒后保留的位数
     * @param time 要处理的日期
     * @param nanoLevel 0~6，对应秒后保留多少位，也即MySQL指定的时间长度
     * @return 处理后的日期
     */
    public static LocalDateTime getDayEndTime(LocalDateTime time, int nanoLevel) {
        Assert.isTrue(nanoLevel >= 0 && nanoLevel < MAX_LOCAL_TIME_ARRAY.length, "nanoLevel取值范围[0~" + (MAX_LOCAL_TIME_ARRAY.length - 1) + "]");
        return time == null ? null : time.with(MAX_LOCAL_TIME_ARRAY[nanoLevel]);
    }

    /**
     * 获取当天开始时刻
     * @param time 要处理的日期
     * @return 处理后的日期
     */
    public static LocalDateTime getDayBeginTime(LocalDate time) {
        return time == null ? null : time.atTime(LocalTime.MIN);
    }

    /**
     * 获取当天开始时刻
     * @param time 要处理的日期
     * @return 处理后的日期
     */
    public static LocalDateTime getDayBeginTime(LocalDateTime time) {
        return time == null ? null : time.with(LocalTime.MIN);
    }

    /**
     * 根据开始和结束日期，生成有序的日期列表
     * @param startDate 开始日期（包含）
     * @param endDate 结束日期（包含）
     * @return 日期列表
     */
    public static List<LocalDate> generateDateList(LocalDate startDate, LocalDate endDate) {
        ValidateDateHelper.checkLegal(startDate, endDate);
        long days = betweenDays(startDate, endDate) + 1;
        List<LocalDate> dateList = new ArrayList<>((int) days);
        LocalDate day = startDate;
        dateList.add(startDate);
        for (int i = 1; i < days; i++) {
            day = day.plusDays(1);
            dateList.add(day);
        }
        return dateList;
    }

    public static Set<LocalDate> generateDateSet(LocalDate startDate, LocalDate endDate) {
        ValidateDateHelper.checkLegal(startDate, endDate);
        long days = betweenDays(startDate, endDate) + 1;
        Set<LocalDate> dates = new HashSet<>((int) days);
        LocalDate day = startDate;
        dates.add(startDate);
        for (int i = 1; i < days; i++) {
            day = day.plusDays(1);
            dates.add(day);
        }
        return dates;
    }

    public static List<DateInfo> generateDateInfos(LocalDate startDate, LocalDate endDate) {
        ValidateDateHelper.checkLegal(startDate, endDate);
        long days = betweenDays(startDate, endDate) + 1;
        List<DateInfo> dateList = new ArrayList<>((int) days);
        dateList.add(new DateInfo().setDate(startDate).calWeekIndex());
        LocalDate day = startDate;
        for (int i = 1; i < days; i++) {
            day = day.plusDays(1);
            dateList.add(new DateInfo().setDate(day).calWeekIndex());
        }
        return dateList;
    }

    /**
     * 计算两时间差值 (end与start必须为同种时间类型)
     * @param end 末位时间[支持LocalDate、LocalDateTime]
     * @param start 起始时间[支持LocalDate、LocalDateTime]
     * @param timeUnit 相差时间单位 <br/>
     *                 [ LocalDate 支持: 年、月、周、日；不支持 小时、分、秒、毫秒]<br/>
     *                 [ LocalDateTime 支持: 年、月、周、日、小时、分、秒、毫秒] <br/>
     * @see ChronoUnit
     * @return 有效值计算[start>end 负数]，无效值返回null
     */
    public static long between(Temporal start, Temporal end, ChronoUnit timeUnit) {
        Assert.isTrue(start != null && end != null, "开始时间和结束时间不能为null");
        Assert.isTrue(timeUnit != null, "时间单位不能为null");
        return timeUnit.between(start, end);
    }

    /**
     * 间隔天数
     * @param startDate 开始
     * @param endDate 结束
     * @return 若日期相同，返回0
     */
    public static Long betweenDays(LocalDate startDate, LocalDate endDate) {
        return between(startDate, endDate, ChronoUnit.DAYS);
    }

    /**
     * 时间戳[毫秒] -> localDate
     * @param timestamp
     * @return
     */
    public static LocalDate timestamp2LocalDate(Long timestamp) {
        return Instant.ofEpochMilli(timestamp).atZone(GMT_PLUS_8).toLocalDate();
    }

    /**
     * 时间戳[毫秒] -> localDateTime
     * @param timestamp
     * @return
     */
    public static LocalDateTime timestamp2LocalDateTime(Long timestamp) {
        return Instant.ofEpochMilli(timestamp).atZone(GMT_PLUS_8).toLocalDateTime();
    }


    /**
     * localDate -> 时间戳[毫秒]
     * @param date
     * @return
     */
    public static Long localDate2timestamp(LocalDate date) {
        return date.atStartOfDay(GMT_PLUS_8).toInstant().toEpochMilli();
    }

    /**
     * localDateTime -> 时间戳[毫秒]
     * @param time
     * @return
     */
    public static Long localDateTime2timestamp(LocalDateTime time) {
        return time.toInstant(GMT_PLUS_8).toEpochMilli();
    }

    /**
     * 获取上周周一
     * @return
     */
    public static LocalDate getPreviousWeek() {
        return getPreviousWeek(LocalDate.now());
    }
    /**
     * 获取上周一
     * @param date
     * @return
     */
    public static LocalDate getPreviousWeek(LocalDate date) {
        DayOfWeek dayOfWeek = date.getDayOfWeek();
        return date.minusDays(7 + dayOfWeek.getValue() - 1);
    }

    /**
     * 获取下周一
     * @return
     */
    public static LocalDate getNextWeek() {
        return getNextWeek(LocalDate.now());
    }

    /**
     * 获取下周一
     * @param date
     * @return
     */
    public static LocalDate getNextWeek(LocalDate date) {
        DayOfWeek dayOfWeek = date.getDayOfWeek();
        return date.plusDays(7 - dayOfWeek.getValue() + 1);
    }

}
